﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;

namespace HttpRequestTool
{
    /// <summary>
    /// Http请求器
    /// </summary>
    public class HttpRequester : IHttpRequester,IDisposable
    {
        #region Get操作

        /// <summary>
        /// Get操作
        /// </summary>
        /// <param name="url">url地址</param>
        /// <param name="param">参数</param>
        /// <param name="cookies">cookie字典</param>
        /// <param name="headers">header字典</param>
        /// <param name="timeout">超时值（以毫秒为单位）</param>
        /// <param name="refererUrl">Referer HTTP 标头的值</param>
        /// <returns></returns>
        public HttpWebResponse Get(string url, Dictionary<string, string> param
            , Dictionary<string, string> cookies, Dictionary<string, string> headers
            , int timeout, string refererUrl )
        {
            Uri uri = new Uri(url);

            param = param == null ? new Dictionary<string, string>() : param;
            string pStr = string.Empty;
            foreach (KeyValuePair<string, string> kvp1 in param)
            {
                pStr += string.Format("{0}{1}={2}", string.IsNullOrEmpty(pStr) ? "" : "&", kvp1.Key, kvp1.Value);
            }

            if (!string.IsNullOrEmpty(pStr))
            {
                url = url + (string.IsNullOrEmpty(uri.Query) ? "?" : "&") + pStr;
            }

            HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
            request.Method = "GET";
            request.Timeout = timeout < 1 ? 600000 //1000*60*10=10 分钟
                : timeout;

            if (!string.IsNullOrEmpty(refererUrl))
            {
                request.Referer = refererUrl;
            }

            //请求头
            if (headers != null)
            {
                foreach (KeyValuePair<string, string> kvp in headers)
                {
                    if (!string.IsNullOrEmpty(kvp.Key))
                    {
                        request.Headers.Add(kvp.Key, kvp.Value);
                    }
                }
            }

            if (cookies != null) {
                CookieCollection cc = new CookieCollection();
                cookies = cookies == null ? new Dictionary<string, string>() : cookies;
                foreach (KeyValuePair<string, string> kvp in cookies)
                {
                    cc.Add(new Cookie(kvp.Key, kvp.Value, "/", uri.Host));
                }
                request.CookieContainer = new CookieContainer();
                request.CookieContainer.Add(cc);
            }
            return (HttpWebResponse)request.GetResponse();
        }
        /// <summary>
        /// Get操作
        /// </summary>
        /// <param name="url">url地址</param>
        /// <param name="param">参数</param>
        /// <param name="cookies">cookie字典</param>
        /// <param name="headers">header字典</param>
        /// <param name="timeout">超时值（以毫秒为单位）</param>
        /// <param name="encoding">字符编码。默认 UTF8 </param>
        /// <param name="refererUrl">Referer HTTP 标头的值</param>
        /// <returns></returns>
        public string Get2(string url, Dictionary<string, string> param
            , Dictionary<string, string> cookies, Dictionary<string, string> headers
            , int timeout, Encoding encoding= null, string refererUrl=null)
        {
            using (HttpWebResponse resp = Get(url, param, cookies, headers, timeout, refererUrl)) {
                using (StreamReader reader = new StreamReader(resp.GetResponseStream(), encoding ==null ? Encoding.UTF8 : encoding ))
                {
                    return reader.ReadToEnd();
                }
            }
        }

        #endregion

        #region Post

        static bool CheckValidationResult(object sender, System.Security.Cryptography.X509Certificates.X509Certificate certificate,
            System.Security.Cryptography.X509Certificates.X509Chain chain,System.Net.Security.SslPolicyErrors errors)
        {
            return true;
        }

        /// <summary>
        /// Post操作
        /// </summary>
        /// <param name="contentType">HttpContentType 枚举</param>
        /// <param name="url">url地址</param>
        /// <param name="param">参数</param>
        /// <param name="cookies">cookie字典</param>
        /// <param name="headers">header字典</param>
        /// <param name="timeout">超时值（以毫秒为单位）默认10分钟 </param>
        /// <param name="encoding">字符编码。默认 UTF8 </param>
        /// <param name="refererUrl">Referer HTTP 标头的值</param>
        /// <returns></returns>
        public HttpWebResponse Post(HttpContentType contentType, string url, object param
            , Dictionary<string, string> cookies, Dictionary<string, string> headers
            , int timeout, Encoding encoding, string refererUrl
            )
        {

            Uri uri = new Uri(url, UriKind.RelativeOrAbsolute);

            //param = param == null ? new Dictionary<string, object>() : param;

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
            request.Method = "POST";
            request.Timeout = timeout < 1 ? 600000 //1000*60*10=10 分钟
                : timeout;

            request.Accept = "application/json, text/javascript, */*; q=0.01";
            request.UserAgent = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36";

            if (!string.IsNullOrEmpty(refererUrl))
            {
                request.Referer = refererUrl;
            }

            //请求头
            if (headers != null)
            {
                foreach (KeyValuePair<string, string> kvp in headers)
                {
                    if (!string.IsNullOrEmpty(kvp.Key))
                    {
                        request.Headers.Add(kvp.Key, kvp.Value);
                    }
                }
            }

            // Cookies
            if (cookies != null)
            {
                CookieCollection cc = new CookieCollection();
                foreach (KeyValuePair<string, string> kvp in cookies)
                {
                    if (!string.IsNullOrEmpty(kvp.Key))
                    {
                        cc.Add(new Cookie(kvp.Key, kvp.Value, "/", uri.Host));
                    }
                }
                request.CookieContainer.Add(cc);
            }

            // https 处理
            if (System.Text.RegularExpressions.Regex.IsMatch(url, "^https://"))
            {
                ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
                ServicePointManager.ServerCertificateValidationCallback = CheckValidationResult;
            }

            //分隔符
            string boundary = null;
            using (MemoryStream mStream = CreateContent(contentType, encoding, param,ref boundary))
            {
                request.ContentLength = mStream.Length;
                switch (contentType)
                {
                    // 键值对方式
                    case HttpContentType.ApplicationXWwwFormUrlencoded:
                        {
                            request.ContentType = "application/x-www-form-urlencoded";
                        }
                        break;
                    // FormData
                    case HttpContentType.MultipartFormData:
                        {
                            request.ContentType = string.Format("multipart/form-data; boundary={0}", boundary);
                        }
                        break;
                    // Json
                    case HttpContentType.ApplicationJson:
                        {
                            request.ContentType = "application/json";
                        }
                        break;
                    // Xml
                    case HttpContentType.TextXml:
                        {
                            request.ContentType = "text/xml";
                        }
                        break;
                    default:
                        {
                            throw new Exception("HttpContentType 类型值异常");
                        }
                }

                // 写入请求
                using (Stream requestStream = request.GetRequestStream())
                {
                    byte[] buffer = new byte[1024];
                    int bytesRead = 0;
                    while ((bytesRead = mStream.Read(buffer, 0, buffer.Length)) != 0)
                    {
                        requestStream.Write(buffer, 0, bytesRead);
                    }
                }

                // 获取响应
                return (HttpWebResponse) request.GetResponse();
            }

        }

        /// <summary>
        /// Post操作
        /// </summary>
        /// <param name="contentType">HttpContentType 枚举</param>
        /// <param name="url">url地址</param>
        /// <param name="param">参数</param>
        /// <param name="cookies">cookie字典</param>
        /// <param name="headers">header字典</param>
        /// <param name="timeout">超时值（以毫秒为单位）默认10分钟 </param>
        /// <param name="encoding">字符编码。默认 UTF8 </param>
        /// <param name="refererUrl">Referer HTTP 标头的值</param>
        /// <returns></returns>
        public string Post2(HttpContentType contentType, string url, object param
            , Dictionary<string, string> cookies, Dictionary<string, string> headers
            , int timeout = 600000, Encoding encoding = null, string refererUrl = null)
        {
            using (HttpWebResponse resp = Post(contentType,url,param,cookies,headers,timeout,encoding, refererUrl) )
            {
                using (StreamReader reader = new StreamReader(resp.GetResponseStream(), encoding == null ? Encoding.UTF8 : encoding))
                {
                    return reader.ReadToEnd();
                }
            }
        }

        /// <summary>
        /// 生成内容
        /// </summary>
        /// <param name="contentType">HttpContentType 枚举</param>
        /// <param name="encoding">字符编码</param>
        /// <param name="paramObj">参数</param>
        /// <param name="boundary">分隔符</param>
        /// <returns></returns>
        public MemoryStream CreateContent(HttpContentType contentType, Encoding encoding, object paramObj,ref string boundary) {
            using (MemoryStream mStream = new MemoryStream()) {
                boundary = null;

                switch (contentType)
                {
                    #region 键值对方式

                    case HttpContentType.ApplicationXWwwFormUrlencoded: {

                            Dictionary<string, object> param = (Dictionary<string, object>) paramObj;
                            string kvData = "";
                            foreach (KeyValuePair<string, object> kvp in param) {
                                if (!string.IsNullOrEmpty(kvp.Key))
                                {
                                    kvData += (kvData == "" ? "" : "&") 
                                        + string.Format("{0}={1}"
                                            , kvp.Key
                                            , kvp.Value is byte[]? encoding.GetString(kvp.Value as byte[])
                                                : Convert.ToString(kvp.Value)
                                           );
                                }
                            }
                            byte[] kvDataBytes = encoding.GetBytes(kvData);
                            mStream.Write(kvDataBytes, 0, kvDataBytes.Length);
                        }
                        break;
                    #endregion

                    #region FormData

                    case HttpContentType.MultipartFormData:
                        {

                            Dictionary<string, object> param = (Dictionary<string, object>)paramObj;

                            //分隔符
                            boundary = "----" + DateTime.Now.Ticks.ToString("x");
                            
                            //文件模板
                            string fileTemplate =
                                "\r\n--" + boundary +
                                "\r\nContent-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"" +
                                "\r\nContent-Type: application/octet-stream" +
                                "\r\n\r\n";
                            //文本模板
                            string textTemplate =
                                "\r\n--" + boundary +
                                "\r\nContent-Disposition: form-data; name=\"{0}\"" +
                                "\r\n\r\n{1}";

                            foreach (KeyValuePair<string, object> kvp in param)
                            {
                                string vData;
                                byte[] vDataBytes;

                                //处理字符串
                                if (kvp.Value is string)
                                {
                                    vData = string.Format(textTemplate,kvp.Key, kvp.Value
                                                );
                                    //第一行不需要换行
                                    vDataBytes = mStream.Length == 0 ? 
                                          Encoding.UTF8.GetBytes(vData.Substring(2, vData.Length - 2)) 
                                        : Encoding.UTF8.GetBytes(vData);

                                    mStream.Write(vDataBytes, 0, vDataBytes.Length);
                                    continue;
                                }

                                //处理文件参数
                                if (kvp.Value.GetType().IsAssignableFrom(typeof(Stream)))
                                {
                                    vData = string.Format(fileTemplate, kvp.Key, kvp.Value);
                                    //第一行不需要换行
                                    vDataBytes = mStream.Length == 0 ?
                                          Encoding.UTF8.GetBytes(vData.Substring(2, vData.Length - 2))
                                        : Encoding.UTF8.GetBytes(vData);

                                    mStream.Write(vDataBytes, 0, vDataBytes.Length);

                                    using (Stream _stream = (Stream)kvp.Value)
                                    {
                                        byte[] buffer = new byte[1024];
                                        int bytesRead = 0;
                                        while ((bytesRead = _stream.Read(buffer, 0, buffer.Length)) != 0)
                                        {
                                            mStream.Write(buffer, 0, bytesRead);
                                        }
                                    }
                                    continue;
                                }

                                //处理 byte[]
                                if (kvp.Value is byte[])
                                {
                                    vData = string.Format(textTemplate
                                                , kvp.Key
                                                , encoding.GetString(kvp.Value as byte[]) 
                                                );
                                    //第一行不需要换行
                                    vDataBytes = mStream.Length == 0 ?
                                          Encoding.UTF8.GetBytes(vData.Substring(2, vData.Length - 2))
                                        : Encoding.UTF8.GetBytes(vData);

                                    mStream.Write(vDataBytes, 0, vDataBytes.Length);
                                    continue;
                                }

                                //处理其他数据
                                vData = string.Format(textTemplate,kvp.Key,Convert.ToString(kvp.Value));
                                //第一行不需要换行
                                vDataBytes = mStream.Length == 0 ?
                                      Encoding.UTF8.GetBytes(vData.Substring(2, vData.Length - 2))
                                    : Encoding.UTF8.GetBytes(vData);

                                mStream.Write(vDataBytes, 0, vDataBytes.Length);
                                
                            }


                            //结尾
                            byte[] endBytes = encoding.GetBytes("\r\n--" + boundary + "--\r\n");
                            mStream.Write(endBytes, 0, endBytes.Length);
                        }
                        break;
                    #endregion

                    #region Json
                    case HttpContentType.ApplicationJson:
                        {
                            string jsonData = paramObj is string ? paramObj as string 
                                : JsonHelper.Serialize(paramObj);
                            byte[] jsonDataBytes = encoding.GetBytes(jsonData);
                            mStream.Write(jsonDataBytes, 0, jsonDataBytes.Length);
                        }
                        break;
                    #endregion

                    #region Xml
                    case HttpContentType.TextXml:
                        {
                            string xmlData = (string)paramObj;
                            byte[] xmlDataBytes = encoding.GetBytes(xmlData);
                            mStream.Write(xmlDataBytes, 0, xmlDataBytes.Length);
                        }
                        break;
                    #endregion
                    default: {
                            throw new Exception("HttpContentType 类型值异常");
                        }        
                }

                return mStream;
            }
        }


        #endregion

        #region 释放资源

        private bool disposedValue;
        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    // TODO: 释放托管状态(托管对象)
                }

                // TODO: 释放未托管的资源(未托管的对象)并重写终结器
                // TODO: 将大型字段设置为 null
                disposedValue = true;
            }
        }

        // // TODO: 仅当“Dispose(bool disposing)”拥有用于释放未托管资源的代码时才替代终结器
        // ~HttpRequester()
        // {
        //     // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中
        //     Dispose(disposing: false);
        // }

        void IDisposable.Dispose()
        {
            // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }

        #endregion


    }

}
